using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace Universe.WinForms
{
    public class ContinualColorPath
    {
        Image _Image;
        Point _Start;
        const PixelFormat format = PixelFormat.Format32bppArgb;
        
        [StructLayout(LayoutKind.Sequential)]
        struct PixelData
        {
            public byte B;
            public byte G;
            public byte R;
            public byte A;

            public PixelData(byte a, byte r, byte g, byte b)
            {
                B = b;
                G = g;
                R = r;
                A = a;
            }
        }

        public ContinualColorPath(Image image, Point start)
        {
            _Image = image;
            _Start = start;
        }
        
        class Parameters
        {
            public readonly Bitmap bmp;
            public readonly BitmapData data;
            public readonly GraphicsPath path;
            public readonly BitArray processed;
            public readonly BitArray found;
            public readonly Color colorStart;
            public PixelData startData;
            public readonly int Width, Height;

            public Parameters(Bitmap bmp, BitmapData data, GraphicsPath path, BitArray processed, BitArray found, Color colorStart)
            {
                this.bmp = bmp;
                this.data = data;
                this.path = path;
                this.processed = processed;
                this.found = found;
                this.colorStart = colorStart;
                Width = bmp.Width;
                Height = bmp.Height;
            }
        }
        
        public GraphicsPath GraphicsPath
        {
            get
            {
                System.Drawing.Drawing2D.GraphicsPath ret = new GraphicsPath();
                Bitmap bmp;
                bool isNew = true;
                Bitmap arg = _Image as Bitmap;
                if (arg != null && arg.PixelFormat == format)
                {
                    isNew = false;
                    bmp = arg;
                }
                else
                {
                    isNew = true;
                    bmp = new Bitmap(_Image.Width, _Image.Height, PixelFormat.Format32bppPArgb);
                    using(Graphics g = Graphics.FromImage(bmp))
                    {
                        g.DrawImage(_Image, 0, 0);
                    }
                }

                Color colorStart = bmp.GetPixel(_Start.X, _Start.Y);
                BitArray processed = new BitArray(bmp.Width * bmp.Height, false);
                BitArray found = new BitArray(bmp.Width * bmp.Height, false);
                BitmapData data =
                    bmp.LockBits(
                        new Rectangle(0, 0, bmp.Width, bmp.Height),
                        ImageLockMode.ReadOnly,
                        PixelFormat.Format32bppPArgb);

                pars = new Parameters(bmp, data, ret, processed, found, colorStart);
                pars.startData = GetPixelData(_Start);
                EvalPoint(_Start);
                
/*
                using(StreamWriter writer = new StreamWriter(@"c:\debug.mem.txt", false))
                {
                    for(int y=0; y<bmp.Height; y++)
                    {
                        for(int x=0; x<bmp.Height; x++)
                        {
                            bool contains = found[x + y*bmp.Width];
                            Color color = GetPixel(new Point(x, y)); // bmp.GetPixel(x, y);
                            string sb = string.Format("{0:X2}{1:X2}{2:X2}{3:X2}", color.A, color.R, color.G, color.B);
                            writer.Write( (contains ? "#" + sb.ToUpper() : "*" + sb.ToLower()) + " ");
                        }
                        
                        writer.WriteLine();
                    }
                }
*/

                bmp.UnlockBits(data);

/*
                using (StreamWriter writer = new StreamWriter(@"c:\debug.net.txt", false))
                {
                    for (int y = 0; y < bmp.Height; y++)
                    {
                        for (int x = 0; x < bmp.Height; x++)
                        {
                            bool contains = found[x + y * bmp.Width];
                            Color color = bmp.GetPixel(x, y);
                            string sb = string.Format("{0:X2}{1:X2}{2:X2}{3:X2}", color.A, color.R, color.G, color.B);
                            writer.Write((contains ? "#" + sb.ToUpper() : "*" + sb.ToLower()) + " ");
                        }

                        writer.WriteLine();
                    }
                }
*/
                
                if (isNew)
                    bmp.Dispose();
                
                return ret;
            }
        }

        static readonly Point[] Offsets =
            new Point[]
                {
                    new Point(0,1), 
                    new Point(1,0), 
                    new Point(-1,0),
                    new Point(0,-1), 
                };
        
        
        static readonly Size OnePixelSize = new Size(1,1);

        unsafe Color GetPixel(Point point)
        {
            IntPtr pointer = (IntPtr)((int)pars.data.Scan0 + (point.Y * pars.data.Stride) + point.X * sizeof(PixelData));
            PixelData* p = (PixelData*)pointer;
            return Color.FromArgb(p->A, p->R, p->G, p->B);
        }

        unsafe PixelData GetPixelData(Point point)
        {
            IntPtr pointer = (IntPtr)((int)pars.data.Scan0 + (point.Y * pars.data.Stride) + point.X * sizeof(PixelData));
            PixelData* p = (PixelData*)pointer;
            PixelData ret = new PixelData(p->A, p->R, p->G, p->B);
            // Marshal.PtrToStructure(pointer, ret);
            return ret;
        }
        
        Parameters pars = null;
        unsafe void EvalPoint(Point16 point)
        {
            if (Check1(point))
            {
                pars.path.AddRectangle(new Rectangle(point, OnePixelSize));
                // pars.found[point.X + point.Y * pars.Width] = true;
                foreach(Point delta in Offsets)
                {
                    bool isPositive = (int)point.X + delta.X >= 0 && (int) point.Y + delta.Y >= 0;
                    if (isPositive)
                    {
                        Point16 next = new Point16((ushort) (point.X + delta.X), (ushort) (point.Y + delta.Y));
                        if (next.X < pars.Width && next.Y < pars.Height)
                        {
                            if (!pars.processed[next.X + next.Y*pars.Width])
                                EvalPoint(next);
                        }
                    }
                }
            }
        }

        struct Point16
        {
            public readonly ushort X, Y;

            public Point16(ushort x, ushort y)
            {
                X = x;
                Y = y;
            }

            public static implicit operator Point(Point16 source)
            {
                return new Point(source.X, source.Y);
            }

            public static implicit operator Point16(Point source)
            {
                return new Point16((ushort)source.X, (ushort) source.Y);
            }
        }
        
        unsafe bool Check1(Point16 point)
        {
            PixelData* p =
                (PixelData*) (IntPtr)
                             ((int) pars.data.Scan0 + (point.Y*pars.data.Stride) + point.X*sizeof (PixelData));
            
            pars.processed[point.X + point.Y * pars.Width] = true;

            return p->R == pars.startData.R
                   && p->G == pars.startData.G
                   && p->B == pars.startData.B;
            
        }
    }
}
